package cc.siyecao.mapper.common.util;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 简单单封装Jackson，实现JSON String<->Java Object的Mapper. 封装不同的输出风格,
 * 使用不同的builder函数创建实例.
 */
public class JsonUtil extends ObjectMapper {
    private static final long serialVersionUID = 1L;
    private static JsonUtil mapper;
    private static final List<String> formarts = new ArrayList<>( 4 );

    private static final String YYYY_MM = "yyyy-MM";
    private static final String YYYY_MM_DD = "yyyy-MM-dd";
    private static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    static {
        formarts.add( YYYY_MM );
        formarts.add( YYYY_MM_DD );
        formarts.add( YYYY_MM_DD_HH_MM );
        formarts.add( YYYY_MM_DD_HH_MM_SS );
    }

    public JsonUtil() {
        this( Include.ALWAYS );
        // Include.Include.ALWAYS 默认
        // Include.NON_DEFAULT 属性为默认值不序列化
        // Include.NON_EMPTY 属性为 空（“”） 或者为 NULL 都不序列化
        // Include.NON_NULL 属性为NULL 不序列化
    }

    public JsonUtil(Include include) {
        // 设置输出时包含属性的风格
        if (include != null) {
            this.setSerializationInclusion( include );
        }
        // 允许单引号 允许不带引号的字段名称
        this.enableSimple();
        // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
        this.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
        // 设置 SerializationFeature.FAIL_ON_EMPTY_BEANS 为 false
        this.configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false );
        // 空格处理为空串
        this.getSerializerProvider().setNullValueSerializer( new JsonSerializer<Object>() {
            @Override
            public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
                    throws IOException {
                jgen.writeString( "" );
            }
        } );
        this.registerModule( new SimpleModule().addSerializer( long.class, ToStringSerializer.instance ) );
        this.registerModule( new SimpleModule().addSerializer( Long.class, ToStringSerializer.instance ) );
        this.registerModule( new SimpleModule().addSerializer( Long.TYPE, ToStringSerializer.instance ) );
        this.registerModule( new SimpleModule().addSerializer( BigDecimal.class, ToStringSerializer.instance ) );
        this.registerModule( new SimpleModule().addSerializer( Date.class, new JsonSerializer<Date>() {
            @Override
            public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider)
                    throws IOException {
                jgen.writeString( DateUtil.date2String( date, YYYY_MM_DD_HH_MM_SS ) );
            }
        } ) );
        this.registerModule( new SimpleModule().addDeserializer( Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser p, DeserializationContext ctxt)
                    throws IOException {
                String source = p.getText();
                if (StringUtils.isBlank( source )) {
                    return null;
                }
                source = source.trim();
                if (source.matches( "^\\d{4}-\\d{1,2}$" )) {
                    return parseDate( source, formarts.get( 0 ) );
                } else if (source.matches( "^\\d{4}-\\d{1,2}-\\d{1,2}$" )) {
                    return parseDate( source, formarts.get( 1 ) );
                } else if (source.matches( "^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$" )) {
                    return parseDate( source, formarts.get( 2 ) );
                } else if (source.matches( "^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$" )) {
                    return parseDate( source, formarts.get( 3 ) );
                } else if (StringUtils.isNumeric( source )) {
                    return new Date( Long.valueOf( source ) );
                } else {
                    throw new IllegalArgumentException( "Invalid false value '" + source + "'" );
                }
            }
        } ) );
        // 设置时区 适用于日期格式统一的项目
//		this.setTimeZone(TimeZone.getDefault());// getTimeZone("GMT+8:00")
//		this.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    }

    /**
     * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用.
     */
    public static JsonUtil getInstance() {
        if (mapper == null) {
            mapper = new JsonUtil().enableSimple();
        }
        return mapper;
    }

    /**
     * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式，建议在内部接口中使用。
     */
    public static JsonUtil nonDefaultMapper() {
        if (mapper == null) {
            mapper = new JsonUtil( Include.NON_NULL );
        }
        return mapper;
    }

    /**
     * Object可以是POJO，也可以是Collection或数组 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]".
     */
    public static String obj2String(Object object) {
        try {
            return getInstance().writeValueAsString( object );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 对象转Json格式字符串(格式化的Json字符串)
     *
     * @param obj 对象
     * @return 美化的Json格式字符串
     */
    public static <T> String obj2StringPretty(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj
                    : getInstance().writerWithDefaultPrettyPrinter().writeValueAsString( obj );
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 反序列化POJO或简单Collection如List<String>. 如果JSON字符串为Null或"null"字符串, 返回Null.
     * 如果JSON字符串为"[]", 返回空集合. 如需反序列化复杂Collection如List<MyBean>,
     * 请使用fromJson(String,JavaType)
     *
     * @see #string2Obj(String, JavaType)
     */
    @SuppressWarnings("unchecked")
    public static <T> T string2Obj(String jsonString) {
        if (StringUtils.isEmpty( jsonString )) {
            return null;
        }
        try {
            return (T) getInstance().readValue( jsonString, Object.class );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 反序列化POJO或简单Collection如List<String>. 如果JSON字符串为Null或"null"字符串, 返回Null.
     * 如果JSON字符串为"[]", 返回空集合. 如需反序列化复杂Collection如List<MyBean>,
     * 请使用fromJson(String,JavaType)
     *
     * @see #string2Obj(String, JavaType)
     */
    public static <T> T string2Obj(String jsonString, Class<T> clazz) {
        if (StringUtils.isEmpty( jsonString )) {
            return null;
        }
        try {
            return getInstance().readValue( jsonString, clazz );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 反序列化复杂Collection如List<Bean>, 先使用函數createCollectionType构造类型,然后调用本函数.
     *
     * @see #createCollectionType(Class, Class...)
     */
    @SuppressWarnings("unchecked")
    public static <T> T string2Obj(String jsonString, JavaType javaType) {
        if (StringUtils.isEmpty( jsonString )) {
            return null;
        }
        try {
            return getInstance().readValue( jsonString, javaType );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 構造泛型的Collection Type如: ArrayList<MyBean>,
     * 则调用constructCollectionType(ArrayList.class,MyBean.class)
     * HashMap<String,MyBean>, 则调用(HashMap.class,String.class, MyBean.class)
     */
    public static JavaType createCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
        return getInstance().getTypeFactory().constructParametricType( collectionClass, elementClasses );
    }

    public JavaType constructJavaType(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType) type;
            Type[] typeArgs = ptype.getActualTypeArguments();
            JavaType[] javaTypes = new JavaType[typeArgs.length];
            for (int i = 0; i < typeArgs.length; i++) {
                javaTypes[i] = constructJavaType( typeArgs[i] );
            }
            return mapper.getTypeFactory().constructParametricType( (Class<?>) ptype.getRawType(), javaTypes );
        } else {
            return mapper.getTypeFactory().constructType( type );
        }
    }

    /**
     * 當JSON裡只含有Bean的部分屬性時，更新一個已存在Bean，只覆蓋該部分的屬性.
     */
    @SuppressWarnings("unchecked")
    public <T> T update(String jsonString, T object) {
        try {
            return this.readerForUpdating( object ).readValue( jsonString );
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False.
     * 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.
     */
    public JsonUtil enableEnumUseToString() {
        this.enable( SerializationFeature.WRITE_ENUMS_USING_TO_STRING );
        this.enable( DeserializationFeature.READ_ENUMS_USING_TO_STRING );
        return this;
    }

    /**
     * 允许单引号 允许不带引号的字段名称
     */
    public JsonUtil enableSimple() {
        this.configure( Feature.ALLOW_SINGLE_QUOTES, true );
        this.configure( Feature.ALLOW_UNQUOTED_FIELD_NAMES, true );
        return this;
    }

    /**
     * 取出Mapper做进一步的设置或使用其他序列化API.
     */
    public ObjectMapper getMapper() {
        return this;
    }

    /**
     * 格式化日期
     *
     * @param dateStr String 字符型日期
     * @param format  String 格式
     * @return Date 日期
     */
    private Date parseDate(String dateStr, String format) {
        Date date;
        try {
            DateFormat dateFormat = new SimpleDateFormat( format );
            date = dateFormat.parse( dateStr );
        } catch (Exception e) {
            throw new IllegalArgumentException( e.getLocalizedMessage() );
        }
        return date;
    }

}
